package cs236369.webserver;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *	This class centers all the XML parsing capabilities 
 * 	In the construction process exceptions may be thrown.
 */
public class Parser {

	Document parsedConfig = null;
	XPath xpath = null;
	
	public Parser(InputStream config) throws
								ParserConfigurationException, 
								SAXException, 
								IOException {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setValidating(false); //TODO ask if it's ok not to validate
		factory.setIgnoringElementContentWhitespace(true); //TODO ask if it's ok to do that
		DocumentBuilder builder = factory.newDocumentBuilder();
		parsedConfig = builder.parse(config);
		
		XPathFactory xpathFactory = XPathFactory.newInstance();
		xpath = xpathFactory.newXPath();
	}
	
	private String getAttributeByParent(String parent,
										String attribute) 
										throws 
										XPathExpressionException {
		return xpath.compile("//" + parent + "/@" + attribute).
						evaluate(parsedConfig);
	}
	
	private String getNodeByParent(String parent,
								String element) 
								throws 
								XPathExpressionException {
		return 	xpath.compile("//" + parent + "/" + element).
									evaluate(parsedConfig);
	}
	
	/**
	 * @return an absolute path in the file system that represents 
	 * 			the route of the base directory of the file system
	 * @throws XPathExpressionException 
	 */
	public String getBaseDirectory() throws XPathExpressionException {
		return getAttributeByParent("server-config", "base");
	}
	
	/**
	 * @return the port the server listens to
	 * @throws XPathExpressionException
	 */
	public int getPort() throws XPathExpressionException {
		String port = getAttributeByParent("server-config", "port");
		return Integer.parseInt(port);
	}
	
	
	/**
	 * @return the number of socket-readers threads
	 * @throws XPathExpressionException
	 */
	public int getNumSocketReaders() throws XPathExpressionException {
		String numSocketReaders = getNodeByParent("socket-readers", "multi");
		return Integer.parseInt(numSocketReaders); 
		
	}
	
	/**
	 * @return the number of request-reader threads
	 * @throws XPathExpressionException
	 */
	public int getNumRequestReaders() throws XPathExpressionException {
		String numRequestReaders = getNodeByParent("request-handlers", "multi");
		return Integer.parseInt(numRequestReaders); 
	}
	
	/**
	 * @return the size of the queue holding the connection socket objects 
	 * @throws XPathExpressionException
	 */
	public int getNumSocketQueue() throws XPathExpressionException {
		String numSockets = getNodeByParent("socket-queue","size");
		return Integer.parseInt(numSockets); 
	}
	
	/**
	 * @return the size of the queue holding the request objects
	 * @throws XPathExpressionException
	 */
	public int getNumRequestQueue() throws XPathExpressionException {
		String numRequests = getNodeByParent("request-queue", "size");
		return Integer.parseInt(numRequests);
	}
	
	public Map<String,String> getExtensions() throws XPathExpressionException {
		
		Map<String,String> extensions = new HashMap<String,String>();

		NodeList typeHandlers = (NodeList) xpath.compile("//type-handler/extension").
										evaluate(parsedConfig, XPathConstants.NODESET);
		
		for (int i=0; i < typeHandlers.getLength(); ++i) {
			
			String key = typeHandlers.item(i).getTextContent();
			String value = xpath.compile("../@class").evaluate(typeHandlers.item(i));
			extensions.put(key, value);
		}
			
		return extensions;
	}
	
	public String getCompilePath(String className) throws XPathExpressionException {
	
		String path = xpath.compile("//type-handler[@class='"+className+"']/parameter/@value")
							.evaluate(parsedConfig);
		return path;
	}
	
	public String getWelcomeFile() throws XPathExpressionException {
		String welcomeFile = xpath.compile("//welcome-file")
									.evaluate(parsedConfig);
		return welcomeFile;
	}
	
	/**
	 * Locates the mime-type in the config file
	 * @param type the name of the type to look
	 * @return the associated mime-type of the passed type, of no such type exists
	 * the method returns "" - a blank string
	 */
	public String getMimeType(String type) throws XPathExpressionException {
		
		NodeList contentTypes = (NodeList)xpath.compile("//mime-mapping/extension").
												evaluate(parsedConfig, XPathConstants.NODESET);
		
		for (int i=0; i < contentTypes.getLength(); ++i) {
			String extension = contentTypes.item(i).getTextContent();
			if(extension.equals(type)) {
				return xpath.compile("./mime-type").
							evaluate(contentTypes.item(i).getParentNode());
			}
		}
		
		return "text/plain"; // we return "" in case the extension couldn't be found
	}
}
